package java_thinking.unit_15._15_8;



import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;

/**
 * @Description :   对于不能创建泛型数组的一般解决方案是在任何想要创建泛型数组的地方
 *                  都使用ArrayList
 * @author: cww
 * @DateTime: 2019-04-05 16:04
 */

public class ListOfGenerics<T> {
    private List<T> array = new ArrayList<>();
    public void add(T item) {
        array.add(item);
    }
    public T get(int index) {
        return array.get(index);
    }
}

class Generic<T> {}

/**
 *
 * 如果非要用数组来创建泛型数组，如下，编译器不会报错，但是永远无法创建这个确切的类型数组
 * 可能会有这样的想法，既然所有数组无论他们持有的类型如何，都具有相同的结构(每个数组槽位的
 * 尺寸和数组的布局)，那么看起来你应该能够创建一个Object数组，并将其转型为所希望的数组类型，
 * 事实上这可以编译，但是不能运行，将产生ClassCaseException
 *
 * 为什么数组不能存储泛型：
 *  问题在于数组将跟踪他们的实际类型，而这个类型是在数组被创建时确定，因此，即使gia已经被转型
 *  为Generic<Integer>[]，但是这个信息只存在编译期,在运行时它仍旧是Object数组,因为有了泛型的擦除
 *  ，数组运行时类型就只能是Object[]，这将引发问题
 */
class ArrayOfGenericRefrence {
    static Generic<Integer>[] gia;
}
class ArrayOfGeneric {
    static final int SIZE = 100;
    static Generic<Integer>[] gia;

    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        //! gia = (Generic<Integer>)new Object[SIZE];
        gia = (Generic<Integer>[])new Generic[SIZE];
        System.out.println(gia.getClass().getSimpleName());
        gia[0] = new Generic<>();
        //编译无法通过
        //! gia[1] = new Object();
        //! gia[2] = new Generic<Double>();
    }
}

/**
 * 成功创建泛型数组的唯一方式就是创建一个被擦除类型的新数组，然后对其转型
 * @param <T>
 */
class GenericArray<T> {
    private T[] array;

    /**
     * 因为有了泛型的擦除，数组运行时类型就只能是Object[]!!!，如果我们在新建数组的时候
     * 立即将其转型为T[]，那么编译期该数组的实际类型将丢失，而编译器可能会错过某些
     * 潜在的错误检查，正因为这样，最好在集合内部使用Object[]，然后在使用数组元素时，
     * 添加一个对T的转型，见ListOfGenerics.java:102
     * @param size
     */
    @SuppressWarnings("unchecked")
    public GenericArray(int size) {
        array = (T[])new Object[size];
        //! array = new T[size];无法这么实现，所以建了上面的Object数组，然后转型
    }
    public void put(int index,T item){
        array[index] = item;
    }
    public T get(int index) {
        return array[index];
    }

    /**
     * 这里返回T[],他将用于main中的array，因此应该是Integer,但是如果调用它，并
     * 尝试着将结果作为Integer[]引用来捕获，就会得到ClassCastException，这是因为实际
     * 运行的类型是Object
     * @return
     */
    public T[] rep() {
        return array;
    }
    public static void main(String[] args) {
        GenericArray<Integer> array = new GenericArray<>(10);
        //这样会报ClassCastException
        Integer[] i = array.rep();
        Object[] oa = array.rep();
    }
}

/**
 * ****************************************
 * 乍眼看没啥差别，只是转型换了个一个地方
 * @param <T>
 */
class GenericArray2<T> {
    private Object[] array;
    public GenericArray2(int size) {
        array = new Object[size];
    }
    public void put(int index,T item) {
        array[index] = item;
    }

    /**
     * 当get被调用时将对象转型为T，实际上是正确的类型，因此是安全的，
     */
    @SuppressWarnings("unchecked")
    public T get(int index) {
        return (T)array[index];
    }

    /**
     * 如果调用rep还是会产生异常，没有任何方式可以推翻底层的数组类型，它只能是Object[]。
     * 在底层代码内部将array当做Object[]而不是T[]处理的优势是：我们不太可能忘记这个数组的运行时类型，
     * 从而导致意外的引入其他缺陷
     */
    @SuppressWarnings("unchecked")
    public T[] rep() {
        //warning unchecked cast
        return (T[])array;
    }

    public static void main(String[] args) {
        GenericArray2<Integer> array2 = new GenericArray2<>(10);
        for (int i =0;i<10;i++) {
            array2.put(i,i);
        }
        for (int i =0;i<10;i++){
            System.out.print(array2.get(i)+" ");
        }
        System.out.println();
        try {
            Integer[] integers = array2.rep();
        } catch (Exception e) {
            System.out.println(e);
        }
    }
}

/**
 * ***********************************************************
 * 查看Java SE5标准类库中的源代码，就会看到从Object数组到参数化类型的转型遍及各处，
 * 如果通读ArrayList.java就会发现它充满了这种转型，如果我们编译他也会发出警告unchecked
 * @param <T>
 */
class GenericArrayWithTypeToken<T> {
    private T[] array;

    /**
     * 类型表计Class<T>被传递到构造器中，以便从擦除中恢复，使得我们可以创建实际类型的数组
     */
    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type,int size) {
        array = (T[]) Array.newInstance(type,size);
    }
    public T get(int index) {
        return array[index];
    }
    public void put(int index,T item) {
        array[index] = item;
    }
    public T[] rep() {
        return array;
    }

    public static void main(String[] args) {
        GenericArrayWithTypeToken<Integer> array = new GenericArrayWithTypeToken<>(Integer.class,10);
        //现在可以直接返回Integer引用了
        Integer[] integers = array.rep();
    }
}